package gameoflive;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferStrategy;
import java.io.PrintWriter;
import java.io.StringWriter;

import javax.swing.JFrame;
import javax.swing.JPanel;

public class Game implements Runnable
{
    final int                         WIDTH       = 1000;
    final int                         HEIGHT      = 700;
    
    private static Game instance;
    
    public static synchronized Game getInstance()
    {
        if (instance == null)
        {
            instance = new Game();
        }
        return instance;
    }
    
    private JFrame                            frame;
    private Canvas                            canvas;
    private BufferStrategy                    bufferStrategy;
    
    public static void log(Object message, boolean newLine)
    {
        System.out.print(message.toString());
        if (newLine)
        {
            System.out.println();
        }
    }
    
    public static void log(Object message)
    {
        log(message, true);
    }
    
    protected Game()
    {
        frame = new JFrame("Basic Game");
        
        JPanel panel = (JPanel) frame.getContentPane();
        panel.setPreferredSize(new Dimension(WIDTH, HEIGHT));
        panel.setLayout(null);
        
        canvas = new Canvas();
        canvas.setBounds(0, 0, WIDTH, HEIGHT);
        canvas.setIgnoreRepaint(true);
        panel.add(canvas);
        
        canvas.addKeyListener(new KeyboardControl());
        canvas.addMouseListener(new MouseControl());
        
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.pack();
        frame.setResizable(false);
        frame.setVisible(true);
        
        canvas.createBufferStrategy(2);
        bufferStrategy = canvas.getBufferStrategy();
        
        canvas.requestFocus();
    }
    
    public void setTitle(String title)
    {
        frame.setTitle(title);
    }
    
    protected boolean IGNORE_MOUSE = true;
    
    private class MouseControl extends MouseAdapter
    {
        @Override
        public void mouseClicked(MouseEvent e)
        {
            if (IGNORE_MOUSE == false)
            {
                java.awt.Point p = e.getPoint();
                int x = (int) (p.getX() / cellSize);
                int y = (int) (p.getY() / cellSize);
                Cell cell = GameOfLive.getInstance().getCell(x, y);
                cell.setSurviving(true);
            }
            
        }
    }
    
    private int  stepsFPS   = 10;
    private long desiredFPS = 60;
    private long desiredDeltaLoop;
    
    private void recalcDesiredDeltaLoop()
    {
        desiredDeltaLoop = (1000 * 1000 * 1000) / desiredFPS;
    }
    
    protected static boolean IGNORE_KEYBOARD       = false;
    
    private boolean   CONSOLE_OPEN          = false;
    private String    CONSOLE_ALLOWED_CHARS = "0123456789.,-_!\"'$%&()[]{}ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\\/#+*";
    
    private class KeyboardControl extends KeyAdapter
    {
        @Override
        public void keyPressed(KeyEvent e)
        {
            if (IGNORE_KEYBOARD == false)
            {
                IGNORE_KEYBOARD = true;
                if (e.getKeyCode() == KeyEvent.VK_F1)
                {
                    CONSOLE_OPEN = CONSOLE_OPEN == false;
                    log("CONSOLE OPEN: " + CONSOLE_OPEN);
                }
                if (CONSOLE_OPEN)
                {
                    if (CONSOLE_ALLOWED_CHARS.indexOf(e.getKeyChar()) >= 0)
                    {
                        
                    }
                }
                else
                {
                    short cellSize = 0;
                    String title = null;
                    switch (e.getKeyCode())
                    {
                        case KeyEvent.VK_MINUS:
                        {
                            if (desiredFPS == 0 || desiredFPS - stepsFPS < 1)
                            {
                                desiredFPS = 1;
                            }
                            else
                            {
                                desiredFPS -= stepsFPS;
                            }
                            title = "-FPS: " + desiredFPS;
                            recalcDesiredDeltaLoop();
                            break;
                        }
                        case KeyEvent.VK_PLUS:
                        {
                            desiredFPS += stepsFPS;
                            title = "+FPS: " + desiredFPS;
                            recalcDesiredDeltaLoop();
                            break;
                        }
                        case KeyEvent.VK_S:
                        {
                            GameOfLive.getInstance().resetGeneration();
                            break;
                        }
                        case KeyEvent.VK_0:
                        {
                            cellSize = 30;
                            break;
                        }
                        case KeyEvent.VK_1:
                        {
                            cellSize = 20;
                            break;
                        }
                        case KeyEvent.VK_2:
                        {
                            cellSize = 10;
                            break;
                        }
                        case KeyEvent.VK_3:
                        {
                            cellSize = 8;
                            break;
                        }
                        case KeyEvent.VK_4:
                        {
                            cellSize = 6;
                            break;
                        }
                        case KeyEvent.VK_5:
                        {
                            cellSize = 4;
                            break;
                        }
                    }
                    if (title != null)
                    {
                        setTitle(title);
                    }
                    if (cellSize > 0)
                    {
                        IGNORE_KEYBOARD = true;
                        log("cellSize: " + (Game.this.cellSize = cellSize));
                        Point size = calcSize();
                        GameOfLive.getInstance().initGeneration(size.getWidth(), size.getHeight());
                    }
                }
                IGNORE_KEYBOARD = false;
            }
        }
    }
    
    boolean running          = true;
    
    public void run()
    {
        long beginLoopTime;
        long endLoopTime;
        long currentUpdateTime = System.nanoTime();
        long lastUpdateTime;
        long deltaLoop;
        recalcDesiredDeltaLoop();
        
        Point size = calcSize();
        GameOfLive.getInstance().initGeneration((int)size.getWidth(), (int)size.getHeight());
        while (running)
        {
            try
            {
                beginLoopTime = System.nanoTime();
                
                render();
                
                lastUpdateTime = currentUpdateTime;
                currentUpdateTime = System.nanoTime();
                update((int) ((currentUpdateTime - lastUpdateTime) / (1000 * 1000)));
                
                endLoopTime = System.nanoTime();
                deltaLoop = endLoopTime - beginLoopTime;
                if (deltaLoop <= desiredDeltaLoop)
                {
                    try
                    {
                        Thread.sleep((desiredDeltaLoop - deltaLoop) / (1000 * 1000));
                    }
                    catch (InterruptedException e)
                    {
                        // Do nothing
                    }
                }
            }
            catch (Exception ex)
            {
                StringWriter sw = new StringWriter();
                ex.printStackTrace(new PrintWriter(sw));
                log(sw.toString());
                running = false;
            }
        }
//        System.exit(0);
    }
    
    private boolean pauseRendering = false;
    
    public void doPauseRendering(boolean pause)
    {
        pauseRendering = pause;
    }
    
    private void render()
    {
        Graphics2D g = (Graphics2D) bufferStrategy.getDrawGraphics();
        clearScreen(g);
        render(g);
        g.dispose();
        bufferStrategy.show();
    }
    
    private short      cellSize = 5;
    
    private Point calcSize()
    {
        int width = WIDTH;
        if (WIDTH % cellSize != 0)
        {
            width -= WIDTH % cellSize;
        }
        width /= cellSize;
        int height = HEIGHT;
        if (HEIGHT % cellSize != 0)
        {
            height -= HEIGHT % cellSize;
        }
        height /= cellSize;
        return new Point(width, height);
    }
    
    /**
     * Rewrite this method for your game
     */
    protected void update(int deltaTime)
    {
        GameOfLive gol = GameOfLive.getInstance();
        if (gol.initGeneration() == false)
        {
            if (gol.prepareNextGeneration() == false)
            {
                gol.resetGeneration();
            }
        }
    }
    
    protected void clearScreen(Graphics2D g)
    {
        Color color = g.getColor();
        g.setColor(Color.WHITE);
        g.fillRect(0, 0, WIDTH, HEIGHT);
        g.setColor(color);
    }
    
    /**
     * Rewrite this method for your game
     */
    protected void render(Graphics2D g)
    {
//        log("render()");
//        int filled = 0;
//        int cleared = 0;
        GameOfLive gol = GameOfLive.getInstance();
        if (gol.initGeneration() == false)
        {
            for (Cell cell : gol.getCells())
            {
                Point position = cell.getPosition();
                if (cell.isAlive())
                {
                    switch (cell.countLivingNeighbors())
                    {
                        case 8:
                        case 7:
                        case 6:
                        case 5:
                        case 4:
                        case 1:
                        case 0:
                        {
                            g.setColor(Color.RED);
                            break;
                        }
                        case 3:
                        case 2:
                        {
                            g.setColor(Color.GREEN);
                            break;
                        }
                        default:
                        {
                            Game.log("??? OBJECT ALIVE WITH NO LIVING NEIGHBORS ???");
                            break;
                        }
                        
                    }
                }
                else
                {
                    g.setColor(Color.BLACK);
                }
                g.fillRect((int) position.getWidth() * cellSize, (int) position.getHeight() * cellSize, cellSize, cellSize);
            }
        }
//        log(filled + " filled; " +  cleared + " cleared");
    }
    
    public static void main(String[] args)
    {
         Thread t = new Thread(Game.getInstance());
         t.start();
    }
    
}
